/**
 * @说明 给fabirc中的元素加入辅助线（网上资料，修改使用）
 * @param {canvas} 参数说明：需要传入 new Canvas() 的那个对象
 * @host https://my.oschina.net/xmqywx/blog/1941539
 * @time 2019.6.12 
*/
import { Canvas, Point, FabricObject, Rect, util, ActiveSelection } from "fabric"

export interface End {
    heading: number,
    // 端点的方向，正上方为0度，正右侧为90度，正下方为180度，正左侧为270度
    // 实际朝向为图形的angle+heading二者之和
    // 只有两个heading完全相反才会吸附
    dx: number,
    dy: number
}

// 端点的绝对位置
interface EndAbsPos {
    left: number,
    top: number,
    angle: number
}

type StateTypes = "" | "main" | "side" // 普通轨道为空串，道岔为main或者side

declare module "fabric" {
    // 确保类的属性不仅在实例对象中可以访问和使用，而且也能在构造函数中被正确识别和初始化
    interface FabricObject {
        id?: string;
        name?: string;
        state: StateTypes;
        ends: End[];
    }
    // 确保导出的对象具有类型化的属性
    interface SerializedObjectProps {
        id?: string;
        name?: string;
        state: StateTypes;
        ends: End[];
    }
}
FabricObject.customProperties = ['id', 'name', "state", "ends",]

//  计算旋转后的相对坐标
function rotatePoint(angle: number, dx: number, dy: number) {
    // 将角度转换为弧度
    var radians = util.degreesToRadians(angle);

    // 计算旋转后的坐标
    var cos = Math.cos(radians);
    var sin = Math.sin(radians);
    var nx = (cos * dx) - (sin * dy);
    var ny = (sin * dx) + (cos * dy);

    return { dx: nx, dy: ny };
}

//  获取端点的绝对坐标
function getEndAbsCoord(bounding: { left: number, top: number, angle: number }, end: End): EndAbsPos {
    // console.log("bounding", bounding)
    const { dx, dy } = rotatePoint(bounding.angle, end.dx, end.dy)
    return {
        left: bounding.left + dx,
        top: bounding.top + dy,
        angle: normalizeAngle(bounding.angle + end.heading)
    }
}

// 获取两点之间的距离
function getDistance(x1: number, y1: number, x2: number, y2: number) {
    return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
// 将角度标准化到0-360度范围内
export function normalizeAngle(angle: number) {
    return ((angle % 360) + 360) % 360;
}
// 判断两个角度是否反向
function areAnglesOpposite(theta1: number, theta2: number) {

    // 值差不多
    function angleEqual(a: number, b: number) {
        return Math.abs(a - b) < 5;
    }

    // 标准化输入的角度
    theta1 = normalizeAngle(theta1);
    theta2 = normalizeAngle(theta2);

    // 检查两个角度是否相反
    // 方向相反意味着它们的角度差为180度，应当相差5度以内。
    return angleEqual(Math.abs(theta1 - theta2), 180) ||
        angleEqual(Math.abs(theta1 + theta2), 360);
}

// 判断theta1需要加多大的角度才能和theta2反向
function deltaDegreesToOpposite(theta1: number, theta2: number): number {
    // 将角度标准化到0-360度范围内
    theta1 = normalizeAngle(theta1);
    theta2 = normalizeAngle(theta2);

    // 计算theta1需要增加多少度才能与theta2方向相反
    let oppositeTheta2 = normalizeAngle(theta2 + 180);
    let degreesToAdd = oppositeTheta2 - theta1;

    // 如果计算出的度数是负数，意味着需要加上360度来达到相反方向
    if (degreesToAdd < 0) {
        degreesToAdd += 360;
    }

    return degreesToAdd;
}

// 围绕某点(相对于左上角)自由旋转
// center: 指要围绕的绝对坐标
function rotateAroundPoint(rect: FabricObject, theta: number, rotationCenter: { x: number, y: number }) {
    console.log("rotating object around", rotationCenter, 'theta', theta)
    console.log('left', rect.left, 'top', rect.top, "w", rect.width, "h", rect.height, "angle", rect.angle)
    // 计算要旋转的对象的中心的绝对坐标，考虑旋转角度
    const cosVal = Math.cos(util.degreesToRadians(rect.angle));
    const sinVal = Math.sin(util.degreesToRadians(rect.angle));
    const centerX = rect.left + (rect.width / 2) * cosVal - (rect.height / 2) * sinVal;
    const centerY = rect.top + (rect.width / 2) * sinVal + (rect.height / 2) * cosVal;
    console.log("centerPos", centerX, centerY, 'sin', sinVal, 'cos', cosVal)
    // 计算相对于对象中心的偏移量
    var offsetX = rotationCenter.x - centerX;
    var offsetY = rotationCenter.y - centerY;
    // console.log("offsetX", offsetX, "offsetY", offsetY)

    // // 考虑对象自身具有的角度
    // offsetX = offsetX * Math.cos(rect.angle) - offsetY * Math.sin(rect.angle);
    // offsetY = offsetX * Math.sin(rect.angle) + offsetY * Math.cos(rect.angle);
    // console.log("offsetX", offsetX, "offsetY", offsetY)
    // 平移对象，使得(relX, relY)成为新的旋转中心
    rect.set({
        left: rect.left + offsetX,
        top: rect.top + offsetY
    });

    // 旋转对象
    rect.rotate(theta + rect.angle);

    // const r = Math.sqrt(Math.pow(offsetX, 2) + Math.pow(offsetY, 2));
    const cosRotate = Math.cos(util.degreesToRadians(theta + rect.angle));
    const sinRotate = Math.sin(util.degreesToRadians(theta + rect.angle));
    const newOffsetX = (rect.width / 2) * cosRotate - (rect.height / 2) * sinRotate;
    const newOffsetY = (rect.width / 2) * sinRotate + (rect.height / 2) * cosRotate;
    console.log("newOffset", newOffsetX, newOffsetY)
    rect.set({
        left: rect.left + newOffsetX,
        top: rect.top + newOffsetY
    })

}


function getActiveEndsCoord(activeObject: FabricObject | ActiveSelection): EndAbsPos[] {
    if (activeObject.type === 'rect' || activeObject.type === "group") {
        return activeObject.ends.map((end) => getEndAbsCoord({ left: activeObject.left, top: activeObject.top, angle: activeObject.angle }, end))
    } else {
        const selection: ActiveSelection = activeObject as ActiveSelection
        // 查找每一个Object的ends有没有重合的地方，判定依据为距离相差2以内。
        // 返回所有未重合的节点的绝对坐标
        const endsInSelection: EndAbsPos[] = []
        // 所有未重合节点相对于选择区域左上端的相对坐标
        const endsRelCoordInSelection: End[] = []
        // 获取选择区域的左上角坐标
        const selectionLeft = selection.left;
        const selectionTop = selection.top;
        // 获取选区中每一个轨道对象的端点的坐标
        const getActiveEndsCoordInSelection = (object: FabricObject) => {
            return object.ends.map((end) => {
                // const {} = object.getCoords()
                const coords = object.getCoords()[0]
                return getEndAbsCoord({
                    left: coords.x,
                    top: coords.y,
                    angle: normalizeAngle(object.angle + selection.angle)
                }, end)
            })
        }
        // TODO: 可以优化循环
        for (const object1 of selection.getObjects()) {
            const obj1Ends = getActiveEndsCoordInSelection(object1)
            for (const obj1End of obj1Ends) {
                let isOverlapping = false
                for (const object2 of selection.getObjects()) {
                    if (object1 === object2) continue // 跳过自身比较
                    const obj2Ends = getActiveEndsCoordInSelection(object2)
                    // console.log("obj1ends", obj1Ends, object1.ends, object1.left, object1.top)
                    // console.log("obj2ends", obj2Ends, object2.ends, object2.left, object2.top)
                    for (const obj2End of obj2Ends) {
                        const distance = getDistance(obj1End.left, obj1End.top, obj2End.left, obj2End.top)
                        if (distance < 2) {
                            isOverlapping = true
                            break
                        }
                    }
                    if (isOverlapping) break
                }
                if (!isOverlapping) {
                    endsInSelection.push(obj1End)
                    // 计算相对坐标
                    const relLeft = obj1End.left - selectionLeft;
                    const relTop = obj1End.top - selectionTop;
                    const relAngle = normalizeAngle(obj1End.angle + selection.angle);
                    endsRelCoordInSelection.push({ heading: relAngle, dx: relLeft, dy: relTop });
                }
            }
        }
        activeObject.ends = endsRelCoordInSelection
        console.log("endsInSelection", endsInSelection, endsRelCoordInSelection)
        return endsInSelection
    }

}

export const initAligningGuidelines = (canvas: Canvas, mouseDownHandler: (e: { x: number, y: number, button: 1 | 2 | 3, target?: FabricObject }) => void) => {

    var ctx = canvas.getSelectionContext(), // getSelectionContext 获取选择上下文
        aligningLineOffset = 5, // 对齐线条偏移
        aligningLineMargin = 4, // 对齐线边距
        aligningLineWidth = 1, // 对齐线条宽度
        aligningLineColor = '#666666', // 颜色
        viewportTransform, // 视图端口转换
        zoom = 1;

    // 端点吸附参考线
    const endsCapturingIndicatorLineStyle = { width: 6, color: '#00ff00' }

    // 画垂直线
    function drawVerticalLine(coords) {
        drawLine(
            coords.x + 0.5,
            coords.y1 > coords.y2 ? coords.y2 : coords.y1,
            coords.x + 0.5,
            coords.y2 > coords.y1 ? coords.y2 : coords.y1);
    }

    // 画水平线
    function drawHorizontalLine(coords) {
        drawLine(
            coords.x1 > coords.x2 ? coords.x2 : coords.x1,
            coords.y + 0.5,
            coords.x2 > coords.x1 ? coords.x2 : coords.x1,
            coords.y + 0.5);
    }

    // 画线
    function drawLine(x1: number, y1: number, x2: number, y2: number, lineWidth: number = 1, strokeStyle: string = '#888888') {
        ctx.save();
        ctx.lineWidth = lineWidth;
        ctx.strokeStyle = strokeStyle;
        ctx.beginPath();
        ctx.moveTo(((x1 + viewportTransform[4]) * zoom), ((y1 + viewportTransform[5]) * zoom));
        ctx.lineTo(((x2 + viewportTransform[4]) * zoom), ((y2 + viewportTransform[5]) * zoom));
        ctx.stroke();
        ctx.restore();
    }

    // 范围
    function isInRange(value1, value2) {
        value1 = Math.round(value1);
        value2 = Math.round(value2);
        for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
            if (i === value2) {
                return true;
            }
        }
        return false;
    }

    var verticalLines = [],
        horizontalLines = [];
    let captureLines: {
        x1: number, y1: number, x2: number, y2: number, referencedEndPos: EndAbsPos,
        activeEndIndex: number,
        activeEndPos: EndAbsPos, activeObject: FabricObject
    }[] = []

    // 移动
    canvas.on('mouse:down', function (options) {
        viewportTransform = canvas.viewportTransform;
        zoom = canvas.getZoom();
        var pointer = canvas.getPointer(options.e);
        var x = pointer.x;
        var y = pointer.y;

        // 在控制台输出坐标
        console.log('点击的坐标是: (' + x + ', ' + y + ')');
        mouseDownHandler({ x, y, button: ((options.e as PointerEvent).button) as 1 | 2 | 3, target: options.target })
    });

    // 对象移动事件（移动到某个点才具有辅助线的功能）
    canvas.on('object:moving', function (e) {

        var activeObject = e.target,
            canvasObjects = canvas.getObjects(),
            activeObjectCenter = activeObject.getCenterPoint(),
            activeObjectLeft = activeObjectCenter.x,
            activeObjectTop = activeObjectCenter.y,
            activeObjectBoundingRect = activeObject.getBoundingRect(),
            activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3],
            activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0],
            horizontalInTheRange = false,
            verticalInTheRange = false,
            transform = canvas._currentTransform;

        if (!transform) return;

        const activeEnds = getActiveEndsCoord(activeObject)
        // console.log(ends)
        // It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
        // but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move

        for (var i = canvasObjects.length; i--;) {

            if (canvasObjects[i] === activeObject) continue;
            if ((activeObject.type === 'selection') && (activeObject as ActiveSelection).contains(canvasObjects[i])) {
                continue
            }
            if (canvasObjects[i].ends == null) {
                continue
            }
            const objEnds = canvasObjects[i].ends.map((end) => getEndAbsCoord({ left: canvasObjects[i].left, top: canvasObjects[i].top, angle: canvasObjects[i].angle }, end))

            // console.log('objEnds', objEnds)
            let foundCaptureLine = false
            for (const objEnd of objEnds) {
                for (let i = 0; i < activeEnds.length; i++) {
                    const activeEnd = activeEnds[i]
                    // if (!areAnglesOpposite(objEnd.angle, activeEnd.angle)) {
                    //     continue
                    // }
                    const distance = getDistance(objEnd.left, objEnd.top, activeEnd.left, activeEnd.top)
                    // console.log(distance, objEnd, activeEnd)
                    if (distance < 20) {
                        captureLines = [{
                            activeEndPos: activeEnd,
                            referencedEndPos: objEnd,
                            activeObject,
                            activeEndIndex: i,
                            x1: activeEnd.left,
                            y1: activeEnd.top,
                            x2: objEnd.left,
                            y2: objEnd.top,
                        }]
                        foundCaptureLine = true
                        break
                    }
                }
                if (foundCaptureLine) {
                    break
                }
            }
            if (!foundCaptureLine) {
                captureLines = []
                continue
            } else {
                break
            }
        }

        if (!horizontalInTheRange) {
            horizontalLines.length = 0;
        }

        if (!verticalInTheRange) {
            verticalLines.length = 0;
        }
    });

    // 结束绘画事件
    canvas.on('before:render', function () {
        canvas.clearContext(canvas.contextTop);
    });

    // 开始选择事件
    canvas.on('selection:created', function (e) {
        console.log("e", e)
    });

    // 开始绘画事件（也就是图形开始变化）
    canvas.on('after:render', function () {
        for (var i = captureLines.length; i--;) {
            const { x1, y1, x2, y2 } = captureLines[i]
            drawLine(x1, y1, x2, y2, endsCapturingIndicatorLineStyle.width, endsCapturingIndicatorLineStyle.color);
        }
    });

    canvas.on('mouse:up', function () {
        console.log(captureLines)
        // verticalLines.length = horizontalLines.length = 0;
        if (captureLines.length > 0) {
            const { activeObject, activeEndPos, referencedEndPos, activeEndIndex } = captureLines[0]
            const activeEnd = activeObject.ends[activeEndIndex]
            console.log("target-angle", referencedEndPos.angle, 'current-angle', activeEndPos.angle)
            const dAngle = deltaDegreesToOpposite(activeEndPos.angle, referencedEndPos.angle)
            activeObject.set({
                angle: activeObject.angle + dAngle
            });

            // rotateAroundPoint(activeObject, dAngle, { x: activeEndPos.left, y: activeEndPos.top })
            // canvas.renderAll()
            // captureLines = []
            // return
            // util.rotate
            // activeObject.rota
            const activeEndPosNew = getEndAbsCoord({ left: activeObject.left, top: activeObject.top, angle: activeObject.angle }, activeEnd)
            const dx = referencedEndPos.left - activeEndPosNew.left
            const dy = referencedEndPos.top - activeEndPosNew.top

            activeObject.set({
                left: activeObject.left + dx,
                top: activeObject.top + dy,
            })
            // 设置控制点的位置，避免控制点发生分离
            activeObject.setCoords()
            captureLines = []
        }
        canvas.renderAll();

    });
}